#ifdef __GNUG__
#pragma implementation
#endif

#include "UnixSystem.h"
#include "Class.h"
#include "Error.h"
#include "ClassManager.h"
#include "Env.h"
#include "Exception.h"
#include "Math.h"
#include "OrdColl.h"
#include "File.h"
#include "UnixPtty.h"

#include "ET_stdio.h"

#define MAXPATHLEN 1024

typedef void (*sigHandlerType)(Signals);
typedef void* (*func)(...);

extern "C" {
	void*       unix_opendir(const char *name);
	const char* unix_getdirentry(void*);
	const char* unix_homedirectory(const char *user= 0);
	int         unix_waitchild();
	int         unix_setitimer(long ms);
	int         unix_myread(FILE*, int, void*, int);
	int         unix_select(u_int nfds, fd_set *readready, fd_set *writeready,
								long timeout);
	void        unix_signal(Signals, sigHandlerType);
	char*       unix_signame(Signals);
	int         unix_filestat(const char *path, u_long *id, u_long *size,
						u_long *flags, u_long *modtime);
	int         unix_TcpConnect(const char *hostname, const char *servicename);
	int         unix_UnixConnect(const char *name);
	int         unix_TcpService(const char *servicename);
	int         unix_UnixService(const char *servername);
	int         unix_ioctl(int fd, int code, void *vp);
	void        unix_nonblock(int fd);
	long        unix_now();

	func        dld_load_more(const char*, const char*, const char*);
	void        dld_dump();
	void        dld_gc();
	u_long      dld_get_bare_symbol(const char*);

	const char *which(const char*, const char*, int= 0);
	const char *getdynpath();

	void        closedir(void*);
	int         accept(int, int, int);
	int         access(const char*, int);
	int         chdir(const char*);
	int         rename(const char*, const char*);
	int         sleep(...);
	int         getpid();
	const char *getwd(const char*);
	char       *gethostname(char *hostname, int size);
	FILE       *popen(const char *cmd, const char *mode);
	int         pclose(FILE*);
	int         system(const char *cmd);
	const char *getenv(const char*);
	int         putenv(const char*);
	int         abort();
	void        exit(int code);
	void        _exit(int code);
}

extern int errno, sys_nerr;
extern char *sys_errlist[];
extern char **environ;

//---- signalHandler -----------------------------------------------------------

void sigHandler(Signals sig)
{
	if (gSystem)
		((UnixSystem*)gSystem)->DispatchSignals(sig);
}

//---- UnixSystem --------------------------------------------------------------

NewMetaImpl(UnixSystem,System, (TSP(environ)));

UnixSystem::UnixSystem() : System("UNIX")
{
}

bool UnixSystem::Init()
{
	if (System::Init())
		return TRUE;

	zombieHandler= new OrdCollection;
	
	//--- install default handlers
	unix_signal(eSigChild, sigHandler);
	unix_signal(eSigBus, sigHandler);
	unix_signal(eSigSegmentationViolation, sigHandler);
	unix_signal(eSigIllegalInstruction, sigHandler);
	unix_signal(eSigSystem, sigHandler);
	unix_signal(eSigPipe, sigHandler);
	unix_signal(eSigAlarm, sigHandler);

	sigcnt= 0;

	gProgPath= strsave(which(getenv("PATH"), gProgname));

	return FALSE;
}

UnixSystem::~UnixSystem()
{
	remove(zombieHandler);

	char **env= environ;

	if (environIsCopied) {
		for (int i= 0; env[i]; i++)
			SafeDelete(env[i]);
		SafeDelete(env);
	}
	SafeDelete(wdpath);
}

//---- Misc --------------------------------------------------------------------

const char *UnixSystem::GetError()
{
	if (errno < 0 || errno >= sys_nerr)
		return form("errno out of range %d", errno);
	return sys_errlist[errno];
}

//---- EventLoop ---------------------------------------------------------------

FileHandler *display;

void UnixSystem::AddFileHandler(FileHandler *h)
{
	System::AddFileHandler(h);
	if (h) {
		register int fd= h->GetFd();
		if (h->HasReadInterest()) {
			readmask.Set(fd);
			maxrfd= Math::Max(maxrfd, fd);
		}
		if (h->HasWriteInterest()) {
			writemask.Set(fd);
			maxwfd= Math::Max(maxwfd, fd);
		}
	}
}

FileHandler *UnixSystem::RemoveFileHandler(FileHandler *h)
{
	FileHandler *oh= System::RemoveFileHandler(h);
	if (oh) {       // found
		readmask.Clr(oh->GetFd());
		writemask.Clr(oh->GetFd());
	}
	return oh;
}

void UnixSystem::AddSignalHandler(SignalHandler *h)
{
	System::AddSignalHandler(h);
	unix_signal(h->GetSignal(), sigHandler);
}

SignalHandler *UnixSystem::RemoveSignalHandler(SignalHandler *h)
{
	return System::RemoveSignalHandler(h);
}

SysEvtHandler *gXDisplay;

void UnixSystem::DispatchOneEvent()
{
	for (;;) {
		if (gXDisplay && gXDisplay->Notify())
			return;
		// check for file descriptors ready for reading/writing
		if (nfd > 0 && fileHandler->Size() > 0) {
			register FileHandler *fh;
			Iter next(fileHandler);
		
			while (fh= (FileHandler*) next()) {
				int fd= fh->GetFd();
				if (fd <= maxrfd && readready.IsSet(fd)) {
					readready.Clr(fd);
					if (fh->ReadNotify())
						return;
				}
				if (fd <= maxwfd && writeready.IsSet(fd)) {
					writeready.Clr(fd);
					if (fh->WriteNotify())
						return;
				}
			}
		}
		readready.Zero();
		writeready.Zero();
		
		// check synchronous signals
		if (sigcnt > 0 && signalHandler->Size() > 0)
			if (CheckSignals(TRUE))
				return;
		sigcnt= 0;
		signals.Zero();

		// check timers
		if (syncTimers && syncTimers->CheckTimer(&syncTimers, unix_now()))
			return;

		// nothing ready, so setup select call
		readready= readmask;
		writeready= writemask; 
		int mxfd= Math::Max(maxrfd, maxwfd)+1;
		nfd= unix_select(mxfd, &readready, &writeready,NextTimeout(syncTimers));
		if (nfd < 0 && nfd != -2) {
			int fd, rc;
			fd_set t;
			for (fd= 0; fd < mxfd; fd++) {
				t.Set(fd);
				if (readmask.IsSet(fd)) {
					rc= unix_select(fd+1, &t, 0, 0);
					if (rc < 0 && rc != -2) {
						fprintf(stderr, "select: read error on %d\n", fd);
						readmask.Clr(fd);
					}
				}
				if (writemask.IsSet(fd)) {
					rc= unix_select(fd+1, &t, 0, 0);
					if (rc < 0 && rc != -2) {
						fprintf(stderr, "select: write error on %d\n", fd);
						writemask.Clr(fd);
					}
				}
				t.Clr(fd);
			}
			//perror("UnixSystem::DispatchOneEvent(select)");
		}
	}
}

//---- handling of system events -----------------------------------------------

void UnixSystem::DispatchSignals(Signals sig)
{
	switch (sig) {
	case eSigAlarm:
		DispatchTimers();
		break;
	case eSigChild:
		CheckChilds();
		return;
	case eSigBus:
	case eSigSegmentationViolation:
	case eSigIllegalInstruction:
		if (gDebug)
			fprintf(stderr, "vor Throw: %d\n", sig);
		Throw(sig);
		fprintf(stderr, "Got signal: %s\n", unix_signame(sig));
		Abort(-1);
		break;
	case eSigSystem:
	case eSigPipe:
		fprintf(stderr, "Signal: %s\n", unix_signame(sig));
		break;
	default:
		signals.Set(sig);
		sigcnt++;
		break;
	}
	
	if (sigcnt && signalHandler->Size() > 0)
		CheckSignals(FALSE);
}

bool UnixSystem::CheckSignals(bool sync)
{
	register SignalHandler *sh;
	{
		Iter next(signalHandler);
	
		while (sh= (SignalHandler*)next()) {
			if (sync == sh->IsSync()) {
				Signals sig= sh->GetSignal();
				if (signals.IsSet(sig)) {
					signals.Clr(sig);
					sigcnt--;
					break;
				}
			}
		}
	}
	if (sh) {
		sh->Notify();
		return TRUE;
	}
	return FALSE;
}

void UnixSystem::CheckChilds()
{
	int pid;
	while ((pid= unix_waitchild()) > 0) {
		Iter next(zombieHandler);
		register UnixPtty *pty;
		while (pty= (UnixPtty*) next())
			if (pty->GetPid() == pid) {
				zombieHandler->RemovePtr(pty);
				pty->DiedNotify();
			}
	}
}

//---- Directories -------------------------------------------------------------

void *UnixSystem::MakeDirectory(const char *name)
{
	return unix_opendir(name);
}

void UnixSystem::FreeDirectory(void *dirp)
{
	if (dirp)
		closedir(dirp);
}

const char *UnixSystem::GetDirEntry(void *dirp)
{
	if (dirp)
		return unix_getdirentry(dirp);
	return 0;
}

bool UnixSystem::ChangeDirectory(const char *path)
{
	bool ret= (bool) (chdir(path) == 0);
	if (wdpath)
		wdpath[0]= 0;   // invalidate path cache
	return ret;
}

const char *UnixSystem::WorkingDirectory()
{
	if (wdpath && wdpath[0])
		return wdpath;
	if (wdpath == 0)
		wdpath= new char[MAXPATHLEN];
	if (getwd(wdpath) == 0) {
		strcpy(wdpath, "/");
		Warning("workingdirectory", "getwd failed");
	}
	return wdpath;
}

const char *UnixSystem::HomeDirectory()
{
	return unix_homedirectory(0);
}

//---- Paths & Files -----------------------------------------------------------

bool UnixSystem::AccessPathName(const char *path, int mode)
{
	if (access(path, mode) == 0)
		return FALSE;
	errorstr= sys_errlist[errno];
	return TRUE;
}

void UnixSystem::Rename(const char *f, const char *t)
{
	::rename(f, t);
	errorstr= sys_errlist[errno];
}

int UnixSystem::GetPathInfo(const char *path, u_long *id, u_long *size,
						u_long *flags, u_long *modtime)
{
	return unix_filestat(path, id,  size, flags, modtime);
}

//---- expand the metacharacters as in the shell -------------------------------

/* expand the metacharacters as in the shell */

static char
	*shellMeta      = "~*[]{}?$",
	*shellStuff     = "(){}<>\"'",
	shellEscape     = '\\';

bool UnixSystem::ExpandPathName(char *patbuf, int buflen)
{
	const char *hd;
	char cmd[170], StuffedPat[200], name[70];
	register char *p, *q;
	FILE *pf;
	int ch, i;

	/* skip leading blanks */
	while (*patbuf == ' ')
		patbuf++;

	/* any shell meta characters ? */
	for (p= patbuf; *p; p++)
		if (strchr(shellMeta, *p))
			goto needshell;

	return FALSE;

needshell:
	/* escape shell quote characters */
	StuffChar(patbuf, StuffedPat, sizeof StuffedPat, shellStuff, shellEscape);

	strcpy(cmd, "echo -n ") ;
	strcat(cmd, " ");

	/* emulate csh -> popen executes sh */
	if (StuffedPat[0] == '~') {
		if (StuffedPat[1] != '\0' && StuffedPat[1] != '/') {
			/* extract user name */
			for (p= &StuffedPat[1], q= name; *p && *p !='/';)
				*q++= *p++;
			*q= '\0';
			hd= unix_homedirectory(name);
			if (hd == 0)
				strcat(cmd, StuffedPat);
			else {
				strcat(cmd, hd);
				strcat(cmd, p);
			}
		} else {
			hd= unix_homedirectory(0);
			if (hd == 0) {
				errorstr= sys_errlist[errno];
				return TRUE;
			}
			strcat(cmd, hd);
			strcat(cmd, &StuffedPat[1]);
		}
	} else
		strcat(cmd, StuffedPat);

	if ((pf= popen(&cmd[0], "r")) == NULL) {
		errorstr= sys_errlist[errno];
		return TRUE;
	}

	/* read first argument */
	for (i= 0, ch= fgetc(pf); ch != EOF && ch != ' '; ch= fgetc(pf)) {
		if (i == buflen-1)
			break;
		patbuf[i++]= ch;
	}
	patbuf[i]= '\0';

	/* overread rest of pipe */
	while (ch != EOF) {
		ch= fgetc(pf);
		if (ch == ' ' || ch == '\t') {
			errorstr= "expression ambigous";
			return TRUE;
		}
	}

	pclose(pf);

	return FALSE;
}

//---- environment manipulation ------------------------------------------------

void UnixSystem::Setenv(const char *name, const char *value)
{
	::putenv(strprintf("%s=%s", name, value));
#if 0
	register int i, len;
	register char **oldenv;

	if (! environIsCopied) {
		oldenv= environ;
		
		for (i= 0; environ[i++]; i++)
			;
		environ= new char* [i+1];
		for (i= 0; oldenv[i]; i++)
			environ[i]= strsave(oldenv[i]);
		environ[i]= 0;
		environIsCopied= TRUE;
	}

	if (value == 0) {
		Warning("Setenv", "with value= 0 not yet implemented");
		return;
	}

	len= strlen(name);
	for (i= 0; environ[i]; i++) {
		if (strncmp(name, environ[i], len) == 0 && environ[i][len]== '=') {
			delete environ[i];
			environ[i]= strprintf("%s=%s", name, value);
			return;
		}
	}

	oldenv= environ;
	environ= new char* [i+2];
	for (i= 0; oldenv[i]; i++)
		environ[i]= oldenv[i];
	environ[i++]= strprintf("%s=%s", name, value);
	environ[i]= 0;
	delete oldenv;
#endif
}

const char *UnixSystem::Getenv(const char *name)
{
	return getenv(name);
}

const char *UnixSystem::GetEnvAt(int ix)
{
	return environ[ix];
}

const char *UnixSystem::Hostname()
{
	if (*hostname == 0)
		gethostname(hostname, sizeof(hostname));
	return hostname;
}

//---- Processes ---------------------------------------------------------------

int UnixSystem::Exec(const char *shellcmd)
{
	return system(shellcmd);
}

FILE *UnixSystem::OpenPipe(const char *command, const char *mode)
{
	return popen(command, mode);
}

int UnixSystem::ClosePipe(FILE *pipe)
{
	return pclose(pipe);
}

int UnixSystem::GetPid()
{
	return ::getpid();
}

void UnixSystem::Exit(int code, bool mode)
{
	if (mode)
		exit(code);
	else
		_exit(code);
}

void UnixSystem::Abort(int)
{
	abort();
}

//---- dynamic loading and linking ---------------------------------------------

#ifndef __GNUG__
#   define __cfront_version__ GNU_CC
#else
#   define __cfront_version__ CFRONT_2_0
#endif

const char *getdynpath()
{
	static const char *dynpath= 0;

	if (dynpath == 0) {
		dynpath= Env::GetValue("System.DynPath", (char*)0);
		if (dynpath == 0) {
			char buf[200];
			sprintf(buf, ".:%s/dynmod", gEtDir);
			dynpath= strsave(buf, -1);
		}
	}
	return dynpath;
}

const char *which(const char *search, const char *file, int mode)
{
	static char name[256];
	register const char *ptr;
	register char *next;

	if (file[0] == '/') {
		if (access(file, mode) == 0)
			return file;
		return 0;
	}

	if (search == 0)
		search= ".";

	for (ptr= search; *ptr;) {
		for (next= name; *ptr && *ptr != ':'; )
			*next++= *ptr++;
		*next= '\0';
		
		if (name[0] != '/') {
			char tmp[200];
			strcpy(tmp, name);
			strcpy(name, gSystem->WorkingDirectory());
			strcat(name, "/");
			strcat(name, tmp);
		}
		strcat(name, "/");
		strcat(name, file);
		
		if (access(name, mode) == 0)
			return name;
		if (*ptr)
			ptr++;
	}
	return 0;
}

Object *UnixSystem::Load(const char *module, const char *entry)
{
	dld_load_more(gProgname, module, entry);
	return 0;
}

//---- Time & Date -------------------------------------------------------------

Time UnixSystem::Now()
{
	return unix_now();
}

void UnixSystem::DispatchTimers()
{
	insideNotify= TRUE;
	if (asyncTimers && asyncTimers->CheckTimer(&asyncTimers, unix_now()))
		if (asyncTimers)
			unix_setitimer(NextTimeout(asyncTimers));
	insideNotify= FALSE;
}

void UnixSystem::AddTimer(Timer *ti)
{
	System::AddTimer(ti);
	if (!insideNotify)  // we are not inside notify
		if (ti->IsAsync() && (ti == asyncTimers))
			unix_setitimer(NextTimeout(asyncTimers));
}

bool UnixSystem::RemoveTimer(Timer *ti)
{
	bool rc= System::RemoveTimer(ti);
	if (ti->IsAsync() && asyncTimers == 0)
		unix_setitimer(-1);
	return rc;
}

//---- RPC ---------------------------------------------------------------------

int UnixSystem::ConnectService(const char *servername, const char *servicename)
{
	char *cp, host[100];
	const char *hp= Getenv("ET_DISPLAY");

	if (hp == 0)
		hp= "unix:ET++";

	strcpy(host, hp);
	if (cp= strchr(host, ':'))
		*cp= '\0';

	if (*host == '\0')
		strcpy(host, "unix");
		
	if (strcmp(host, "unix") == 0 || strcmp(host, Hostname()) == 0)
		return unix_UnixConnect(servername);
	return unix_TcpConnect(host, servicename);
}

int UnixSystem::OpenConnection(const char *server, const char *service)
{
	bool started= FALSE;

	const char *appcomm= Env::GetValue("System.AppComm", (char*)0);
	if (appcomm == 0)
		return -1;

	for (int i= 0; i < 3; i++) {
		int fd= ConnectService(server, service);
		if (fd >= 0)
			return fd;
		if (!started) {
			Exec(form("%s &", appcomm));
			started= TRUE;
		}
		sleep(1);
	}
	return -1;
}

int UnixSystem::AnnounceTcpService(const char *servicename)
{
	return unix_TcpService(servicename);
}

int UnixSystem::AnnounceUnixService(const char *servername)
{
	return unix_UnixService(servername);
}

int UnixSystem::AcceptConnection(int sock)
{
	return accept(sock, 0, 0);
}

//---- pseudo ttys -------------------------------------------------------------

PttyImpl *UnixSystem::MakePttyImpl()
{
	UnixPtty *pty= new UnixPtty;
	if (zombieHandler)
		zombieHandler->Add(pty);
	return pty;
}

